local FADE_FRAMES = 5
local FADE_INTENSITY = .6
local FADE_RADIUS = 1
local FADE_FALLOFF = .8
local Text = require "widgets/text"

local function OnUpdateFlicker(inst, starttime)
    local time = (GetTime() - starttime) * 30
    local flicker = (math.sin(time) + math.sin(time + 2) + math.sin(time + 0.7777)) * .5 -- range = [-1 , 1]
    flicker = (1 + flicker) * .5                                                         -- range = 0:1
    inst.Light:SetRadius(FADE_RADIUS + .1 * flicker)
end

local function OnUpdateFade(inst)
    local k
    if inst._fade:value() <= FADE_FRAMES then
        inst._fade:set_local(math.min(inst._fade:value() + 1, FADE_FRAMES))
        k = inst._fade:value() / FADE_FRAMES
    else
        inst._fade:set_local(math.min(inst._fade:value() + 1, FADE_FRAMES * 2 + 1))
        k = (FADE_FRAMES * 2 + 1 - inst._fade:value()) / FADE_FRAMES
    end

    inst.Light:SetIntensity(FADE_INTENSITY * k)
    inst.Light:SetRadius(FADE_RADIUS * k)
    inst.Light:SetFalloff(1 - (1 - FADE_FALLOFF) * k)

    if TheWorld.ismastersim then
        inst.Light:Enable(inst._fade:value() > 0 and inst._fade:value() <= FADE_FRAMES * 2)
    end

    if inst._fade:value() == FADE_FRAMES or inst._fade:value() > FADE_FRAMES * 2 then
        inst._fadetask:Cancel()
        inst._fadetask = nil
    end

    if inst._fade:value() == FADE_FRAMES then
        if inst._flickertask == nil then
            inst._flickertask = inst:DoPeriodicTask(.1, OnUpdateFlicker, 0, GetTime())
        end
    elseif inst._flickertask ~= nil then
        inst._flickertask:Cancel()
        inst._flickertask = nil
    end
end

local function OnFadeDirty(inst)
    if inst._fadetask == nil then
        inst._fadetask = inst:DoPeriodicTask(FRAMES, OnUpdateFade)
    end
    OnUpdateFade(inst)
end

local function FadeIn(inst, instant)
    if instant then
        inst._fade:set(FADE_FRAMES)
        OnFadeDirty(inst)
    else
        inst._fade:set(
            inst._fade:value() <= FADE_FRAMES and
            inst._fade:value() or
            math.max(0, 2 * FRAMES + 1 - inst._fade:value())
        )
        if inst._fadetask == nil then
            inst._fadetask = inst:DoPeriodicTask(FRAMES, OnUpdateFade)
        end
    end
end

local function FadeOut(inst, instant)
    if instant then
        inst._fade:set(FADE_FRAMES * 2 + 1)
        OnFadeDirty(inst)
    else
        inst._fade:set(
            inst._fade:value() > FADE_FRAMES and
            inst._fade:value() or
            2 * FADE_FRAMES + 1 - inst._fade:value()
        )
        if inst._fadetask == nil then
            inst._fadetask = inst:DoPeriodicTask(FRAMES, OnUpdateFade)
        end
    end
end

local function CanFade(inst)
    return inst.parent ~= nil and inst.parent.components and inst.parent.components.inventoryitem and inst.parent.components.equippable
        and (not inst.parent.components.inventoryitem:IsHeld() or inst.parent.components.equippable:IsEquipped())
end

local function OnIsDay(inst, isday, delayed)
    if inst._daytask ~= nil then
        if not delayed then
            inst._daytask:Cancel()
        end
        inst._daytask = nil
    end
    if CanFade(inst) then
        if not delayed then
            inst._daytask = inst:DoTaskInTime(2 + math.random(), OnIsDay, isday, true)
        elseif isday then
            FadeOut(inst)
        else
            FadeIn(inst)
        end
    end
end

local function OnDropped(inst)
    if inst._daytask ~= nil then
        inst._daytask:Cancel()
        inst._daytask = nil
    end
    if not TheWorld.state.isday and CanFade(inst) then
        FadeIn(inst)
    else
        FadeOut(inst, true)
    end
end

local function OnPutInInventory(inst)
    if inst._daytask ~= nil then
        inst._daytask:Cancel()
        inst._daytask = nil
    end
    FadeOut(inst, true)
end

local function OnLoad(inst)
    if inst._daytask ~= nil then
        inst._daytask:cancel()
        inst._daytask = nil
    end
    if CanFade(inst) then
        if TheWorld.state.isday then
            FadeOut(inst, true)
        else
            FadeIn(inst, true)
        end
    end
end

local common_postinit = function(inst)
    inst.entity:AddLight()

    inst.Light:SetFalloff(FADE_FALLOFF)
    inst.Light:SetIntensity(FADE_INTENSITY)
    inst.Light:SetRadius(FADE_RADIUS)
    inst.Light:SetColour(200 / 255, 100 / 255, 170 / 255)
    inst.Light:Enable(false)
    inst.Light:EnableClientModulation(true)

    inst._fade = net_smallbyte(inst.GUID, "nuts_candybag._fade", "fadedirty")
    inst._fade:set(FADE_FRAMES * 2 + 1)

    if not TheWorld.ismastersim then
        inst:ListenForEvent("fadedirty", OnFadeDirty)
    end
end

local master_postinit = function(inst)
    inst:WatchWorldState("isday", OnIsDay)
    OnLoad(inst)

    inst.FadeIn = OnDropped
    inst.FadeOut = OnPutInInventory

    inst.OnLoad = OnLoad
end


return {
    common_postinit = common_postinit,
    master_postinit = master_postinit
}
